<?php

namespace App\Implement;

use Closure;
use App\Components\Log\Dump;
use App\Contract\Event\ExceptionCatchableEventInterface;
use App\Contract\Event\GoCoroutineEventInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;

class EventDispatcher implements EventDispatcherInterface
{
    private ListenerProviderInterface $listeners;

    private Dump $logger;

    public function __construct(
        ListenerProviderInterface $listeners,
        Dump                      $logger
    )
    {
        $this->listeners = $listeners;
        $this->logger = $logger;
    }

    /**
     * Provide all listeners with an event to process.
     *
     * @param object $event
     * @return object The Event that was passed, now modified by listeners
     * Notes: 在原框架基础上对事件增加个两个特性，可携程化，非异常化
     * User: 陈朋
     * DateTime: 2023/2/20 10:57
     */
    public function dispatch(object $event): object
    {
        foreach ($this->listeners->getListenersForEvent($event) as $listener) {
            if (
                ($event instanceof GoCoroutineEventInterface && $event->isUseCoroutine()) &&
                ($event instanceof ExceptionCatchableEventInterface && $event->isAutoCatch())
            ) {
                $this->dispatchWithCoroutine($this->dispatchWithCatch($this->run($listener, $event), $this->getCatcher($event)))();
            } else if ($event instanceof GoCoroutineEventInterface && $event->isUseCoroutine()) {
                $this->dispatchWithCoroutine($this->run($listener, $event))();
            } else if ($event instanceof ExceptionCatchableEventInterface && $event->isAutoCatch()) {
                $this->dispatchWithCatch($this->run($listener, $event), $this->getCatcher($event))();
            } else {
                $this->run($listener, $event)();
            }
            $this->dump($listener, $event);
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                break;
            }
        }
        return $event;
    }

    /**
     * Dump the debug message if $logger property is provided.
     * @param mixed $listener
     */
    private function dump($listener, object $event)
    {
        return;
        $eventName = get_class($event);
        $listenerName = '[ERROR TYPE]';
        if (is_array($listener)) {
            $listenerName = is_string($listener[0]) ? $listener[0] : get_class($listener[0]);
        } elseif (is_string($listener)) {
            $listenerName = $listener;
        } elseif (is_object($listener)) {
            $listenerName = get_class($listener);
        }
        $this->logger->event(sprintf('Event %s handled by %s listener.', $eventName, $listenerName));
    }

    /**
     * Notes: 执行闭包并捕获异常
     * User: 陈朋
     * DateTime: 2023/2/20 10:37
     * @param Closure $closure
     * @return Closure
     */
    private function dispatchWithCatch(Closure $closure, Closure $catcher): Closure
    {
        return function () use ($closure, $catcher) {
            try {
                $closure();
            } catch (\Throwable $throwable) {
                $catcher()($throwable);
            }
        };
    }

    /**
     * Notes: 执行处理逻辑
     * User: 陈朋
     * DateTime: 2023/2/20 10:56
     * @param $listener
     * @param $event
     * @return Closure
     */
    private function run($listener, $event): Closure
    {
        return function () use ($listener, $event) {
            $listener($event);
        };
    }

    /**
     * Notes: 使用协程执行回调函数
     * User: 陈朋
     * DateTime: 2023/2/20 10:38
     * @param Closure $closure
     * @return Closure
     */
    private function dispatchWithCoroutine(Closure $closure): Closure
    {
        return function () use ($closure) {
            go(function () use ($closure) {
                $closure();
            });
        };
    }

    /**
     * Notes: 获取捕获闭包
     * User: 陈朋
     * DateTime: 2023/2/20 10:55
     * @param ExceptionCatchableEventInterface $event
     * @return Closure
     */
    private function getCatcher(ExceptionCatchableEventInterface $event): Closure
    {
        return function () use ($event) {
            return [$event, 'catch'];
        };
    }
}